﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Roslyn.Compilers.Common;
using Roslyn.Compilers.CSharp;

namespace FernUniHagen.CompilerExtension.Functions
{
    /// <summary>
    /// Contains all the functions where Roslyn classes miss something.
    /// e.g.: FindDeclaration would be good to call directly on the semantic model
    /// </summary>
    public static class Extensions
    {
        /// <summary>
        /// A Little helper function to support calls like statementList.AddParams(newAssignStmt, assignExpr, conditionStmt, newAssignment);
        /// </summary>
        /// <typeparam name="T">generic type of the list</typeparam>
        /// <param name="list">the list where the items are added</param>
        /// <param name="items">an arra of items or several items comma seperated</param>
        public static void AddRange<T>(this List<T> list, params T[] items)
        {
            
            var itemsToAdd = new List<T>();
            foreach (var item in items)
                itemsToAdd.Add(item);
            list.AddRange(itemsToAdd);
        }
     
        /// <summary>
        /// FROM http://stackoverflow.com/questions/58744/best-way-to-copy-the-entire-contents-of-a-directory-in-c-sharp
        /// </summary>
        /// <param name="sourceDir"></param>
        /// <param name="destinationPath"></param>
        /// <param name="overwrite"></param>
        /// <returns></returns>
        public static DirectoryInfo CopyTo(this DirectoryInfo sourceDir, string destinationPath, bool overwrite = false)
        {
            var sourcePath = sourceDir.FullName;

            var destination = new DirectoryInfo(destinationPath);

            destination.Create();

            foreach (var sourceSubDirPath in Directory.EnumerateDirectories(sourcePath, "*", SearchOption.AllDirectories))
                Directory.CreateDirectory(sourceSubDirPath.Replace(sourcePath, destinationPath));

            foreach (var file in Directory.EnumerateFiles(sourcePath, "*", SearchOption.AllDirectories))
            {
                //Continue copying
                try {  
                    File.Copy(file, file.Replace(sourcePath, destinationPath), overwrite);
                }catch(Exception ex)
                { }
            }
            return destination;
        }

        /// <summary>
        /// from  http://stackoverflow.com/questions/222598/how-do-i-clone-a-generic-list-in-c last entry
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="listToClone"></param>
        /// <returns></returns>
        public static ICollection<T> Clone<T>(this ICollection<T> listToClone)
        {
            var array = new T[listToClone.Count];
            listToClone.CopyTo(array, 0);
            return array.ToList();
        }


        /// <summary>
        /// Finds the declaration of the provided identifier
        /// </summary>
        /// <param name="model">the semantic model which was created for the document</param>
        /// <param name="identifier">the identifier e.g. 'variable' to which we want to find the definition</param>
        /// <returns>The defining syntax node</returns>
        public static CommonSyntaxNode FindDecleration(this ISemanticModel model, IdentifierNameSyntax identifier)
        {
            CommonSymbolInfo symbolInfo = model.GetSymbolInfo(identifier);
            ISymbol symbol = symbolInfo.Symbol;
            ISymbol originalDefinition = symbol.OriginalDefinition;

            //The code is somewhere else and we can't do anything
            if (symbol.Locations.FirstOrDefault().IsInMetadata) return null;
            //Bad but recommended here http://stackoverflow.com/questions/11428662/how-to-find-the-syntaxnode-for-a-method-symbol-in-a-compilationunit
            var position = symbol.Locations.First().SourceSpan.Start;

            //Check if this is needed or if we can just return the parent
            var node = GetDeclarationType(symbol.Locations.First().SourceTree.GetRoot().FindToken(position).Parent);

            return node;
        }


        /// <summary>
        /// TODO:Need to fix for all cases (not only for the first)
        /// according to roslyn-csharp-syntax-inheritance-diagram, Fields, Methods and Properies inherit from memberdeclarationsyntax.
        /// use it? what about parameters?  
        /// </summary>
        /// <param name="pSem"></param>
        /// <param name="exprName"></param>
        /// <returns></returns>
        public static CommonSyntaxNode FindDecleration(this ISemanticModel pSem, SimpleNameSyntax exprName)
        {
            var declNode = pSem.GetSymbolInfo(exprName).Symbol.OriginalDefinition.DeclaringSyntaxNodes;
            //Consider throwing an exception
            if (declNode == null || declNode.Count == 0 ) //case it's a call to a member not defined in this solution
            {
                return null;
            }
           
            //TODO: Docu names partial classes as an examle for multiple declarations. 
            //Need to check if also for parameters, fields, properties and methods multiple decarations can exist. If so, we would need to find the one with the multiplicity annotated
            //if (declNode.Count > 1)
            //    return null; 


            return declNode[0].GetDeclarationType();
        }

        /// <summary>
        /// Returns information about the type of the nood (which interfaces are there etc.)
        /// </summary>
        /// <param name="model"></param>
        /// <param name="node"></param>
        /// <returns></returns>
        public static ITypeSymbol GetTypeInformation(this ISemanticModel model, CommonSyntaxNode node)
        {
            var typeInfo = model.GetTypeInfo(node);
            ITypeSymbol nodeType = typeInfo.Type;
            return nodeType;
        }

        /// <summary>
        /// TODO: Move to factory?
        /// Returns typed declaration node (including Parameter Syntax) 
        /// </summary>
        /// <param name="node">the node where we want to now the type from</param>
        /// <returns>Null if node is not a declaration node (e.g.: Field,Property, Parameter, Method, Class) or the specific type </returns>
        public static CommonSyntaxNode GetDeclarationType(this CommonSyntaxNode node)
        {
            CommonSyntaxNode returnNode = null;
            FieldDeclarationSyntax annotatedField = node.FirstAncestorOrSelf<FieldDeclarationSyntax>();
            PropertyDeclarationSyntax annotatedProperty = node.FirstAncestorOrSelf<PropertyDeclarationSyntax>();
            ParameterSyntax annotatedParameter = node.FirstAncestorOrSelf<ParameterSyntax>();
            VariableDeclarationSyntax variable = node.FirstAncestorOrSelf<VariableDeclarationSyntax>();
            MethodDeclarationSyntax annotatedMethod = node.FirstAncestorOrSelf<MethodDeclarationSyntax>();
            ClassDeclarationSyntax aClass = node.FirstAncestorOrSelf<ClassDeclarationSyntax>();
            if (annotatedField != null)
            {
                returnNode = annotatedField;
            }
            else if (annotatedProperty != null)
            {
                returnNode = annotatedProperty;
            }
            else if (annotatedParameter != null)
            {
                returnNode = annotatedParameter;
            }
            else if(variable != null)
            {
                returnNode = variable;
            }
            else if (annotatedMethod != null)
            {
                returnNode = annotatedMethod;
            }
            else if (aClass != null)
            {
                returnNode = aClass;
            }
            return returnNode;
        }
    }
}
